
class SingletonLazy{

    private volatile static SingletonLazy instance = null;
    /*public static SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }*/

    // 把懒汉模式变成线程安全的
    // 要加锁,并且要知道加锁位置
    public static SingletonLazy getInstance(){
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    // 但是这样的解决方法还是有问题的
    // 这就导致每次getInstance 都需要加锁,加锁操作是有开销的.真的需要每次加锁么?

    // 仔细想想,这里的加锁 只是在new 出对象之前加上的,是有必要的.
    // 一旦new完了,后续调用getInstance,
    // 此时 instance 的值 一定是一个非空的,因此就会触发return
    // 相当于一个是比较操作 一个是返回操作
    // 这两个操作都是读操作 (此时不加锁也没事)

    // 所以基于上述讨论 就给上述代码加一个判定
    // 如果对象还没创建,才加锁
    // 如果对象已经创建过了,就不需要加锁了
    // 要注意两个if是长得一样的,但是起到完全不一样的效果,这两个中间多了一个加锁操作,这让这两个代码执行的时间需要间隔很久
    // 而在这时间间隔内 其他线程可能早已把 instance 的值改变了

    // 而即使解决了加锁问题,但是上述的代还是有问题的,就有可能会出现内存可见性问题
    // 假设有很多线程,都去进行 grtInstance ,这个时候, 代码是否会被优化的风险? (只有第一次读才是真正的读了内存,后续都是寄存器/cache)
    // 内存可见性问题
    // 另外 还会涉及到 指令重排序问题
    // instance = new SingletonLazy(); 这一句 是要拆分成三个步骤
    // 1.申请内存空间
    // 2.调用构造方法,把这个内存空间初始化成一个合理的对象
    // 3.把内存空间的地址赋值给instance引用
    // 正常情况下 都是按照以上三步的顺序执行的

    // 但是这时编译器还有一手操作,指令重排序
    // 为了提高程序效率,调整代码执行顺序
    //变成 1 3 2 这个顺序
    // 1 是肯定先执行的
    // 因为 2 3 这两个操作 是单线程的角度来看是没有区别的
    // (如果单线程 123 和 132 就没有本质区别了)
    // 但是这种操作在多线程的情况下就是有问题的了
    // 当 t1 执行完 1 3 之后 , 执行 2 之前 被 切出 CPU 了
    //t2来执行
    // 当 t1 执行完  3 之后 ,t2 看起来,此处就是非空了
    // 此时此刻,t2 就相当于直接返回了 instance 的引用
    // 并且可能会尝试使用引用中的属性
    // 但是由于 t1 中的 2 操作还没有执行完呢.t2 拿到的是一个非法的对象,还没有构造完成的不完整的对象

    // 这两个问题的解决办法就是 :
    // volatile
    // volatile 有两个功能:
    // 1.解决内存可见性
    // 2.不允许 指令重排序

    // !!!!!所以要注意有三个步骤!!!!!!

    private SingletonLazy(){}
}
public class ThreadCase2 {
    // 单例模式的实现2:懒汉模式
    public static void main(String[] args) {
        SingletonLazy singletonLazy = SingletonLazy.getInstance();
        SingletonLazy singletonLazy1 = SingletonLazy.getInstance();
        System.out.println(singletonLazy == singletonLazy1); // 不管怎么样去调用都是同一个实例

        // 为什么叫懒汉实例呢 :  这个实例并非是类加载的时候创建的,而是真正第一次使用的时候才去创建(如果不用的话就不去创建了);

    }
}
